π SPACE Client SDK for React
The SPACE React SDK provides fully-typed React/TypeScript components, hooks, and utilities to seamlessly integrate your web apps with SPACE.
With this SDK you can:
- β‘ Connect a React application to a SPACE instance (HTTP + WebSocket).
- π Generate and manage Pricing Tokens directly in the client.
- π§© Activate/deactivate UI components declaratively with the
<Feature>
component. - π Subscribe to SPACE pricing events to keep your UI in sync.
This SDK is intended for research and experimentation. For production usage, see License & Disclaimer.
β οΈ Important Noteβ
- Feature evaluation is always based on the Pricing Token.
space-react-client
does not call SPACE directly to check features; instead, it uses the evaluation results stored in the Pricing-Token loaded inTokenService
. (See SPACE communication protocol for more details on how pricing tokens work.)
π¦ Installationβ
Install with your package manager of choice:
npm install space-react-client
# or
yarn add space-react-client
# or
pnpm add space-react-client
Peer dependencies:
react >= 18
react-dom >= 18
β‘ Quick Startβ
The minimal setup to connect to SPACE, load a userβs Pricing Token, and render UI conditionally with <Feature>
.
1. Wrap your app with SpaceProvider
β
import React from 'react';
import { createRoot } from 'react-dom/client';
import { SpaceProvider } from 'space-react-client';
import App from './App';
const config = {
url: 'http://localhost:5403', // Your SPACE instance URL
apiKey: 'YOUR_API_KEY', // API key issued by SPACE
allowConnectionWithSpace: true,
};
createRoot(document.getElementById('root')!)
.render(
<SpaceProvider config={config}>
<App />
</SpaceProvider>
);
Setting allowConnectionWithSpace: false
disables all connections to SPACE.
This means you can still evaluate features from a token, but event listeners (e.g., pricing_created, pricing_archived, etc.) as well as methods like setUserId
and generateUserPricingToken
will not work.
2. Identify the user and load a Pricing Tokenβ
import { useEffect } from 'react';
import { useSpaceClient } from 'space-react-client';
export function App() {
const spaceClient = useSpaceClient();
useEffect(() => {
spaceClient.setUserId('user-123')
.then(() => console.log("User's pricing token set"))
.catch(console.error);
// Listen for SPACE sync events
const onSync = () => console.log('Connected & synchronized with SPACE');
spaceClient.on('synchronized', onSync);
return () => spaceClient.off('synchronized', onSync);
}, [spaceClient]);
return <YourComponent />;
}
3. Gate UI with <Feature>
β
import { Feature, On, Default, Loading, ErrorFallback } from 'space-react-client';
export function OnlineVisitsButton() {
return (
<Feature id="petclinic-visits">
<On>
{/* Rendered when feature is enabled */}
<button>Start online visit</button>
</On>
<Default>
{/* Rendered when feature is disabled */}
<button disabled>Upgrade to enable online visits</button>
</Default>
<Loading>
{/* Rendered while evaluating */}
<span>Checking your planβ¦</span>
</Loading>
<ErrorFallback>
{/* Rendered on error */}
<span>Could not verify your access permission to online visits.</span>
</ErrorFallback>
</Feature>
);
}
π Alternative: Token-only mode (no live connection)β
Set allowConnectionWithSpace: false
to disable the WebSocket client.
You can then inject a Pricing Token from your backend:
import { useEffect } from 'react';
import { usePricingToken } from 'space-react-client';
export function InjectTokenFromServer() {
const tokenService = usePricingToken();
useEffect(() => {
fetch('/api/my-pricing-token')
.then(res => res.text()) // token as string
.then(token => tokenService.updatePricingToken(token))
.catch(console.error);
}, [tokenService]);
return <YourComponent />;
}
π API Referenceβ
Providersβ
-
SpaceProvider({ config, children })
Initializes the client and provides context.- Props:
config: SpaceConfiguration
url: string
β SPACE instance URLapiKey: string
β Authentication key emitted by SPACEallowConnectionWithSpace: boolean
β Iffalse
, event listeners take no effect (default:true
)
children: React.ReactNode
- Props:
Hooksβ
-
useSpaceClient(): SpaceClient
Returns the connectedSpaceClient
instance. Throws if not available. -
useTokenService(): TokenService
Returns theTokenService
instance that is managing the lifecycle of pricing tokens. Available even if live connection is disabled. -
usePricingTokenPayload(): Record<string, any> | null
Returns the parsed payload of the current pricing token, ornull
if none is set or invalid. Could be useful to maintain the UI in sync with pricing token.
UI Componentsβ
-
<Feature id="feature-id">β¦</Feature>
Declarative feature gating.- Subcomponents:
<On>
β Rendered when feature evaluates totrue
.<Default>
β Rendered when feature evaluates tofalse
.<Loading>
β Rendered while evaluating.<ErrorFallback>
β Rendered on errors (invalid id, expired token, etc).
- Subcomponents:
The feature-id
is a string in the format saasName-featureName
, where saasname is always lowercase.
For example, to reference the pets feature from PetClinic, the resulting feature-id would be: petclinic-pets
.
Client APIβ
SpaceClient
is instantiated by SpaceProvider
.
Attributes:
- httpUrl:
string
β Configured base URL of the SPACE instance. π Example:http://localhost:5403/api/v1
- wsUrl:
string
β Configured WebSocket URL of the SPACE instance. π Example:ws://localhost:5403/api/v1/events
- apiKey:
string
β Configured API key for authentication. π Example:6a0f4f1093c1e95616efd61b69b15f90f1b25953a4201961995b8a89035aac72
- token:
TokenService
β Instance of the Token Service being employed. could be useful when working inallowConnectionWithSpace: true
mode to not need theuseTokenService
hook.
Methods:
on(event, callback)
β Listen to SPACE events.off(event?, callback?)
β Remove listeners. If no args, removes all listeners.setUserId(userId)
β Set user for evaluations, generates a pricing token, and stores it.generateUserPricingToken()
β Generate and return a fresh Pricing Token for the user configured withsetUserId
. It does not store the token.
β Supported Events
synchronized
β Client is connected and synced with SPACE.pricing_created
β A new pricing was added.pricing_activated
β A pricing moved from archived β active.pricing_archived
β A pricing moved from active β archived.service_disabled
β A service was disabled.error
β Connection or processing error.
All events (except synchronized
and error
) include the following object:
{
serviceName: string; // REQUIRED: The name of the service that triggered the event
pricingVersion?: string; // OPTIONAL: The version of the pricing involved
}
Token Serviceβ
Methods:
update(token: string): void
β Validates & stores a pricing token.getPayload(): Record<string, any> | null
β Return parsed token payload.getKey(key: string): any | null
β Search the key within the token payload and returns its value.evaluateFeature(featureId: string): boolean | null
β Returnstrue | false | null
.subscribe(listener: () => void): () => void
β Subscribe to token changes. Returns an unsubscribe function.
Token expectations:
exp: number
β UNIX expiration.features: Record<string, { eval: boolean; limit?: number | null; used?: number | null }>
pricingContext: Record<string, {features: Record<string, string|boolean>, usageLimits: Record<string, number|boolean>}> | null
β Optional object containing the pricingContext considered for evaluation. It represents the configuration the user has access to.subscriptionContext: Record<string, number> | null
β Optional object containing the subscriptionContext considered for evaluation.
π‘οΈ Security Considerationsβ
- Do not expose SPACE API keys in production.
Instead, issue Pricing Tokens from your backend and deliver them to the client. - Tokens are validated client-side with the
exp
claim. Rotate or shorten TTLs as needed.
βοΈ Development & Toolingβ
The project uses the following main tools and technologies:
π License & Disclaimerβ
This project is licensed under the MIT License. See LICENSE.
This SDK is part of ongoing research in pricing-driven devops. It is still in an early stage and not intended for production use.
β FAQβ
Q: Do I need SPACE running to use this?
A: Yes, for live connectivity. In token-only mode, you just need a Pricing Token from your backend.
Q: Why does useSpaceClient
throw?
A: Likely because youβre outside SpaceProvider
, or allowConnectionWithSpace
is false
.
Q: Whatβs the format for feature IDs?
A: A feature id must:
- Always include a dash (
-
). - Match exactly the keys present in the Pricing Token payload.
The format is built internally as: saasName-featureName
, where saasname is always lowercase.
For example, if you want to instantiate the feature pets
from the SaaS PetClinic, the feature id would be: petclinic-pets
.